/**
 * 这是基于寄存器的虚拟机
 * 
 * 寄存器 R1,R2,R3,R4
 * 
 * 操作数类型
 *  D 立即数据
 *  V 变量
 *  R 寄存器
 *  
 * pc : 当前执行的指令位置(指令是个数组，pc表达当前执行到的位置)
 * 
 * move a[DVR],b[R] 移动指令 a处数据移动到寄存器b处
 * dw a[V],b[R] 数据写入 a = b
 * add a[R],b[R],c[R] 加发运算 c = a + b
 * sub a[R],b[R],c[R] 减法运算 c = a - b
 * mul a[R],b[R],c[R] 乘法运算 c = a * b
 * div a[R],b[R],c[R] 除法运算 c = a / b
 * jmp a[R] 跳转指令 pc = a
 * ofsjmp a[R] 偏移跳转 pc += a
 * cjmp a[R],c[R] 条件跳转 if(a == true) jmp b else jmp c
 * ofscjmp a[R],c[R] 条件跳转 if(a == true) ofsjmp b else ofsjmp c
 * eq a[R],b[R],c[R] 条件运算 c = (a == b)
 * neq a[R],b[R],c[R] 条件跳转: c = (a != b)
 * le a[R],b[R],c[R] 条件跳转：c = (a < b)
 * gr a[R],b[R],c[R] 条件跳转：c = (a > b)
*/

/*
    指令类型

    A : OPCODE DVR R
    B : OPCODE V R
    C : OPCODE R R R
    D : OPCODE R
*/

/*

        a = 100;
        b = 200;
        c = a + b;
    转换-->
        数据区：
            a : 100
            b : 200;
            c : 0
        指令区
            move a,R1
            move b,R2
            add R1,R2,R3

        a = 100;
        b = 0;
        if(a < 200)
        {
            b = a * 30;
        }
    转换-->
        数据区：
            a : 100
            b : 0;
        指令区
            move a,R1
            move b,R2
            le R1,R2,R3
            ofscjmp R3,1,3
                move a,R1
                move 30,R2
                mul
            
*/

using System;
using System.Collections.Generic;
using TBit.Lib;

namespace TBit {
    public enum RegisterIndex {
        R1,
        R2,
        R3,
        R4,
        RET,
        CP,
    }

    public class VMRunError : Exception {
        public VMRunError(string msg) : base(msg) {}
    }

    public class VM {
        public static Dictionary<Opcode,Func<VM,Instruction,int>> InstructionDefines = new Dictionary<Opcode, Func<VM,Instruction, int>>();
        public const int REGISTER_NUMBER = 6;
        public Dictionary<string,int> datas = new Dictionary<string, int>();
        public Dictionary<int,string> memory_name_hash = new Dictionary<int,string>();
        public List<Data> memory = new List<Data>();
        public Stack<Data> call_stack = new Stack<Data>();
        public List<Instruction> instructions = new List<Instruction>();
        public Data[] registers = new Data[VM.REGISTER_NUMBER];
        public int pc = 0;
        public Dictionary<string,LibFunc> libfuncs = new Dictionary<string, LibFunc>();

        public Data GetData(string name) {
            if(!datas.ContainsKey(name)) return new Data();
            int mem = datas[name];
            return memory[mem];
        }

        public void WriteData(string name,Data data) {
            if(datas.ContainsKey(name))
            {
                int mem = datas[name];
                memory[mem] = data;
            }
            else
            {
                int mem = memory.Count;
                memory.Add(data);
                datas[name] = mem;
                memory_name_hash[mem] = name;
            }
        }

        public void MemoryPush(Data data) {
            call_stack.Push(data);
        }

        public Data MemoryPop() {
            return call_stack.Pop();
        }

        public Data GetRegister(RegisterIndex register_index) {
            return registers[(int)register_index];
        }

        public void WriteRegister(RegisterIndex register_index,Data data){
            registers[(int)register_index] = data;
        }

        public void RunInstruction(Instruction inst)
        {
            if(InstructionDefines.ContainsKey(inst.opcode))
            {
                var func = InstructionDefines[inst.opcode];
                var add = func(this,inst);
                this.pc =  this.pc + add;
            }
        }

        public void PushInstructon(Instruction inst) {
            instructions.Add(inst);
        }

        public bool RunNext() {
            if(pc < 0 || pc >= instructions.Count) {
                Reset();
                return false;
            }
            var inst = instructions[pc];
            RunInstruction(inst);
            return true;
        }

        public void Reset() {
            pc = 0;
        }

        public void AddLibfunc(LibFunc libfunc) {
            libfuncs.Add(libfunc.name,libfunc);
        }

        public void CallLibfunc(string name) {
            if(libfuncs.ContainsKey(name))
            {
                LibFunc func = libfuncs[name];
                List<Data> args = new List<Data>();
                for(int i = 0;i < func.arg_number;i++)
                {
                    args.Add(call_stack.Pop());
                }
                WriteRegister(RegisterIndex.RET,func.Call(args));
            }
        }

        public void Init() {
            InstructionDefines[Opcode.MOV] = Instrcutions.Mov.Run;
            InstructionDefines[Opcode.DD]  = Instrcutions.DD.Run;
            InstructionDefines[Opcode.DW]  = Instrcutions.Dw.Run;
            InstructionDefines[Opcode.ADD] = Instrcutions.Add.Run;
            InstructionDefines[Opcode.SUB] = Instrcutions.Sub.Run;
            InstructionDefines[Opcode.MUL] = Instrcutions.Mul.Run;
            InstructionDefines[Opcode.DIV] = Instrcutions.Div.Run;
            InstructionDefines[Opcode.GR] = Instrcutions.Gr.Run;
            InstructionDefines[Opcode.LE] = Instrcutions.Le.Run;
            InstructionDefines[Opcode.EQ] = Instrcutions.Eq.Run;
            InstructionDefines[Opcode.NEQ] = Instrcutions.Neq.Run;
            InstructionDefines[Opcode.JMP] = Instrcutions.Jmp.Run;
            InstructionDefines[Opcode.OFSJMP] = Instrcutions.Ofsjmp.Run;
            InstructionDefines[Opcode.CJMP] = Instrcutions.Cjmp.Run;
            InstructionDefines[Opcode.OFSCJMP] = Instrcutions.Ofscjmp.Run;
            InstructionDefines[Opcode.PUSH] = Instrcutions.Push.Run;
            InstructionDefines[Opcode.POP] = Instrcutions.Pop.Run;
            InstructionDefines[Opcode.POPR] = Instrcutions.Popr.Run;
            InstructionDefines[Opcode.CALL] = Instrcutions.Call.Run;
            InstructionDefines[Opcode.DF] = Instrcutions.DF.Run;
            InstructionDefines[Opcode.RET] = Instrcutions.Ret.Run;
            InstructionDefines[Opcode.AND] = Instrcutions.And.Run;
            InstructionDefines[Opcode.OR] = Instrcutions.Or.Run;
            InstructionDefines[Opcode.NOT] = Instrcutions.Not.Run;

            for(int i = 0;i< REGISTER_NUMBER;i++)
            {
                registers[i] = new Data();
            }

            AddLibfunc(new Print());
            AddLibfunc(new ReadInput());
        }

        public void Run() {
            while(RunNext());
        }
    }
}